/**
 *
 * \file File.cpp
 *	\brief File declaration class
 * \author Edouard ROGE
 * \date 15 septembre 2010
 *
 */

// to disable warnings from windows compilation
#define  _CRT_SECURE_NO_WARNINGS

#include <stdarg.h>
#include <stdlib.h>
#include "Core/Log/LogManager.h"
#include "File.h"

namespace Core
{
	namespace File
	{
		uint32 File::ms_iBufferSize = 64;

		File::File()
			: m_iFlags(0), m_pFile(NULL), m_URI(), m_mutex()
		{

		}

		File::File(const std::string & _path)
			: m_iFlags(0), m_pFile(NULL), m_URI(_path)
		{

		}

		File::File(const URI & _URI)
			: m_iFlags(0), m_pFile(NULL), m_URI(_URI)
		{

		}

		File::File(const File & _file)
			: m_iFlags(0), m_pFile(NULL), m_URI(_file.GetURI())
		{

		}

		File::File(char * _path)
			: m_iFlags(0), m_pFile(NULL), m_URI(_path)
		{

		}

		File::File(const char * _path)
			: m_iFlags(0), m_pFile(NULL), m_URI(_path)
		{

		}

		File::~File()
		{
			if (IsFlagged(eFile_Open))
			{
				std::string filename = m_URI.GetFilename() + m_URI.GetFileExtension();
				LOG_ERROR("File %s still open", filename.c_str());
				Close();
			}
		}

		bool File::Open(uint32 _iFileFlags)
		{
			std::lock_guard<std::mutex> lock(m_mutex);
			m_iFlags = _iFileFlags;

			if (IsFlagged(eFile_Open))
			{
				LOG_ERROR("File already Open");
				return false;
			}

			std::string strRWMode;


			// Security checks
			if (IsFlagged(eFile_Write) && IsFlagged(eFile_Append))
			{
				std::string filename = m_URI.GetFilename() + m_URI.GetFileExtension();
				LOG_WARNING("File %s cannot be opened for writing and appending at the same time", filename.c_str());
				m_iFlags &= ~eFile_Write;
			}
			if (IsFlagged(eFile_Text) && IsFlagged(eFile_Binary))
			{
				std::string filename = m_URI.GetFilename() + m_URI.GetFileExtension();
				LOG_ERROR("File %s cannot be opened as text and binary file at the same time", filename.c_str());
				return false;
			}

			if (IsFlagged(eFile_Read) && IsFlagged(eFile_Write))
				strRWMode = "w+";
			else if (IsFlagged(eFile_Read) && IsFlagged(eFile_Append))
				strRWMode = "a+";
			else if (IsFlagged(eFile_Read))
				strRWMode = "r";
			else if (IsFlagged(eFile_Write))
				strRWMode = "w";
			else if (IsFlagged(eFile_Append))
				strRWMode = "a";

			if (IsFlagged(eFile_Binary))
				strRWMode += 'b';
			else
				m_iFlags |= eFile_Text;

			m_pFile = fopen(m_URI.GetURI().c_str(), strRWMode.c_str());

			if (!m_pFile)
			{
				LOG_ERROR("File %s not found", m_URI.GetURI().c_str());
				return false;
			}

			m_iFlags |= eFile_Open;

			return true;
		}

		bool File::Open(const std::string & _strURI, uint32 _iFileFlags)
		{
			m_URI = _strURI;
			return Open(_iFileFlags);
		}

		bool File::Close()
		{
			if (!IsFlagged(eFile_Open))
			{
				LOG_WARNING("File %s is already closed", m_URI.GetURI().c_str());
				return false;
			}

			if (m_pFile)
			{
				fclose(m_pFile);
				m_iFlags &= ~eFile_Open;
			}

			return true;
		}

		bool File::ReadLine(std::string & _str)
		{
			LOG_ASSERT(IsFlagged(eFile_Text), "ReadLine() function only reads text files");
			if (IsFlagged(eFile_Text))
			{
				_str.clear();
				char c;
				do
				{
					(*this) >> c;
					if (c == '\n')
						return true;
					if (c == EOF)
						return false;
				} while (c != EOF);
			}

			return false;
		}


		File & File::operator>>(int32 & _i)
		{
			if (IsFlagged(eFile_Text))
			{
				if (fscanf(m_pFile, "%d", &_i) == 0)
					_i = 0;
			}
			else
			{
				if (fread(&_i, 1, sizeof(int), m_pFile) == 0)
					_i = 0;
			}

			return (*this);
		}

		File & File::operator>>(uint32 & _i)
		{
			if (IsFlagged(eFile_Text))
			{
				if (fscanf(m_pFile, "%d", &_i) == 0)
					_i = 0;
			}
			else
			{
				if (fread(&_i, 1, sizeof(uint32), m_pFile) == 0)
					_i = 0;
			}
			return (*this);
		}

		File & File::operator>>(float & _f)
		{
			if (IsFlagged(eFile_Text))
			{
				if (fscanf(m_pFile, "%f", &_f) == 0)
					_f = 0.f;
			}
			else
			{
				if (fread(&_f, 1, sizeof(float), m_pFile) == 0)
					_f = 0.f;
			}
			return (*this);
		}

		File & File::operator>>(double & _d)
		{
			if (IsFlagged(eFile_Text))
			{
				if (fscanf(m_pFile, "%lf", &_d) == 0)
					_d = 0.0;
			}
			else
			{
				if (fread(&_d, 1, sizeof(double), m_pFile) == 0)
					_d = 0.0;
			}
			return (*this);
		}

		File & File::operator>>(char & _c)
		{
			if (IsFlagged(eFile_Text))
			{
				_c = fgetc(m_pFile);
			}
			else
			{
				if (fread(&_c, 1, sizeof(char), m_pFile) == 0)
					_c = '0';
			}
			return (*this);
		}

		void File::Read(char * _str, uint32 _iSize)
		{
			if (fread(_str, _iSize, sizeof(char), m_pFile) == 0)
			{
				_str[0] = '\0';
			}
		}

		template<class T> File & File::Read(const T & _value)
		{
			return this->operator >> (_value);
		}

		void File::Write(const char * _fmt, ...)
		{
			LOG_ASSERT(IsFlagged(eFile_Text), "Write() function only writes text files");
			if (IsFlagged(eFile_Text))
			{
				va_list ap;
				va_start(ap, _fmt);

				char buf[256];
				vsprintf(buf, _fmt, ap);

				va_end(ap);

				fprintf(m_pFile, "%s", buf);
			}
		}

		File & File::operator<<(int32 _i)
		{
			if (IsFlagged(eFile_Text))
				fprintf(m_pFile, "%d", _i);
			else
				fwrite(&_i, 1, sizeof(int), m_pFile);
			return (*this);
		}

		File & File::operator<<(uint32 _i)
		{
			if (IsFlagged(eFile_Text))
				fprintf(m_pFile, "%d", _i);
			else
				fwrite(&_i, 1, sizeof(uint32), m_pFile);
			return (*this);
		}

		File & File::operator<<(float _f)
		{
			if (IsFlagged(eFile_Text))
				fprintf(m_pFile, "%f", _f);
			else
				fwrite(&_f, 1, sizeof(float), m_pFile);
			return (*this);
		}

		File & File::operator<<(double _d)
		{
			if (IsFlagged(eFile_Text))
				fprintf(m_pFile, "%f", _d);
			else
				fwrite(&_d, 1, sizeof(double), m_pFile);
			return (*this);
		}

		File & File::operator<<(char _c)
		{
			if (IsFlagged(eFile_Text))
				fprintf(m_pFile, "%c", _c);
			else
				fwrite(&_c, 1, sizeof(char), m_pFile);
			return (*this);
		}

		File & File::operator<<(const std::string & _str)
		{
			if (IsFlagged(eFile_Text))
				fprintf(m_pFile, "%s", _str.c_str());
			else
				fwrite(_str.c_str(), _str.length(), sizeof(double), m_pFile);
			return (*this);
		}

		bool File::IsFlagged(uint32 _iFlags) const
		{
			return (m_iFlags & _iFlags) != 0;
		}

		void File::Write(char * _str, uint32 _iSize)
		{
			if (IsFlagged(eFile_Text))
				fprintf(m_pFile, "%s", _str);
			else
				fwrite(_str, _iSize, sizeof(char), m_pFile);
		}

		void File::Write(const char * _str, uint32 _iSize)
		{
			if (IsFlagged(eFile_Text))
				fprintf(m_pFile, "%s", _str);
			else
				fwrite(_str, _iSize, sizeof(char), m_pFile);
		}

		template<class T> File & File::Write(const T & _value)
		{
			return this->operator << (_value);
		}
	}
}
